Skip to content

refactor git-autograder to generalize bebavior and reduce indirections#21

Open
SAN-MUYUN wants to merge 7 commits intogit-mastery:mainfrom
SAN-MUYUN:refactor/generalise-behavior
Open

refactor git-autograder to generalize bebavior and reduce indirections#21
SAN-MUYUN wants to merge 7 commits intogit-mastery:mainfrom
SAN-MUYUN:refactor/generalise-behavior

Conversation

@SAN-MUYUN
Copy link
Copy Markdown

@SAN-MUYUN SAN-MUYUN commented Mar 9, 2026

Fixes #5 , fixes #6 , fixes #8
refactor code to generalize behavior and reduce indirections

RFC:

@SAN-MUYUN SAN-MUYUN marked this pull request as draft March 9, 2026 03:32
@jovnc jovnc requested a review from Copilot March 9, 2026 04:58
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors git-autograder to expose tag-related functionality through the repo abstraction and adds small utilities for working with commits and underlying GitPython objects.

Changes:

  • Introduces GitAutograderTag and TagHelper to query local/remote tags and resolve tag→commit.
  • Extends the repo abstraction (GitAutograderRepoBase, GitAutograderRepo, NullGitAutograderRepo) with a tags helper.
  • Adds convenience accessors: GitAutograderCommit.message, CommitHelper.commit_from_message, and GitAutograderExercise.git_repo.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/git_autograder/tag.py Adds a wrapper type for GitPython tag refs with convenience properties.
src/git_autograder/helpers/tag_helper.py Adds tag querying utilities (local tags, remote tag names, tag→commit helpers).
src/git_autograder/repo/repo_base.py Adds tags to the repo interface.
src/git_autograder/repo/repo.py Wires TagHelper into the concrete repo implementation.
src/git_autograder/repo/null_repo.py Adds tags property to the null repo (currently with an incorrect error message).
src/git_autograder/helpers/commit_helper.py Adds helper to find a commit by message.
src/git_autograder/commit.py Adds decoded message accessor for commits.
src/git_autograder/exercise.py Adds git_repo property for accessing the underlying Repo when not ignored.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@property
def tags(self) -> TagHelper:
raise AttributeError(
"Cannot access attribute files on NullGitAutograderRepo. Check that your repo_type is not 'ignore'."
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AttributeError message in tags is copy/pasted from files (it says "Cannot access attribute files"), which will mislead users. Update it to reference tags (and consider matching the ignore/local-ignore wording used elsewhere, e.g. GitAutograderExercise.git_repo).

Suggested change
"Cannot access attribute files on NullGitAutograderRepo. Check that your repo_type is not 'ignore'."
"Cannot access attribute tags on NullGitAutograderRepo. Check that your repo_type is not 'ignore'."

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@desmondwong1215 desmondwong1215 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments.

@property
def tags(self) -> TagHelper:
raise AttributeError(
"Cannot access attribute files on NullGitAutograderRepo. Check that your repo_type is not 'ignore'."
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be tags instead of files in the error message.

Comment on lines +18 to +38
names: List[str] = []
seen = set()

for line in raw.splitlines():
parts = line.split()
if len(parts) != 2:
continue

ref = parts[1]
if not ref.startswith("refs/tags/"):
continue

name = ref[len("refs/tags/") :]
if name.endswith("^{}"):
name = name[:-3]

if name not in seen:
seen.add(name)
names.append(name)

return names
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason that we need both names and seen for this method? Is it for maintaining the order?
Actually, we don't need the names list; we can add valid names to seen, and convert the seen set to a list as the return value.

contents = [
line.strip() for line in input_file.readlines() if line.strip() != ""
]
if contents != expected:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are comparing a list[str] against str, which will always return False.

Comment on lines +66 to +68
def points_to(self, tag_name: str, commit: GitAutograderCommit) -> bool:
tag_commit = self.commit_or_none(tag_name)
return tag_commit is not None and tag_commit.hexsha == commit.hexsha
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method looks like a behaviour of GitAutograderTag instead of TagHelper to me.

Comment on lines +60 to +64
def commit_or_none(self, tag_name: str) -> Optional[GitAutograderCommit]:
t = self.tag_or_none(tag_name)
if t is None:
return None
return t.commit
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this method? We will check whether the tag exists before retrieving its commit in our verify logic.

@SAN-MUYUN SAN-MUYUN marked this pull request as ready for review March 14, 2026 18:49
@SAN-MUYUN
Copy link
Copy Markdown
Author

@desmondwong1215 Really appreciate the feedbacks! I have updated the PR accordingly, feel free to test it out and review again.

Copy link
Copy Markdown

@desmondwong1215 desmondwong1215 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave some comments. Everything else LGTM!

Comment on lines +50 to +53
with (
open(given, "r") as given_file,
open(expected, "r") as expected_file,
):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason that we are not using file() or file_or_none() here?

Comment on lines +54 to +56
contents = given_file.read().replace("\n", "")
expected_contents = expected_file.read().replace("\n", "")
return contents == expected_contents
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current implementation, a\nbc and ab\nc will be equal, but their content should be different. Is this the intended implementation?

Copy link
Copy Markdown
Author

@SAN-MUYUN SAN-MUYUN Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done intentionally, since we do not want to punish users because of variation in line breaks. As long as the actual content is the same, we are good with it.
Anyways, for easy future integration, I will add a boolean field for developers to decide whether they want exact content or not.

Comment on lines +32 to +33
if name not in names:
names.append(name)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the ordering of the tag important in this case, as using set will be more efficient?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I find using of set is unnecessary, just like you pointed out in the previous review. For a single exercise, it is unlikely that we have large number of tags such that we will need to use a set to keep track of the tags. It would be good to keep the ordering in some cases.

@SAN-MUYUN
Copy link
Copy Markdown
Author

@desmondwong1215 fixed the are_files_equal method accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants